package com.wing.oauth2.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wing.common.constant.SecurityConstants;
import com.wing.common.utils.JsonResult;
import com.wing.oauth2.details.userdetails.warehouse.WarehouseUserDetailsServiceImpl;
import com.wing.oauth2.exception.OAuth2ExceptionHandler;
import com.wing.oauth2.extension.warehouse.WarehouseTokenGranter;
import com.wing.oauth2.filter.CustomClientCredentialsTokenEndpointFilter;
import com.wing.oauth2.extension.captcha.CaptchaTokenGranter;
import com.wing.oauth2.extension.mobile.SmsCodeTokenGranter;
import com.wing.oauth2.extension.refresh.PreAuthenticatedUserDetailsService;
import com.wing.oauth2.extension.wechat.WechatTokenGranter;
import com.wing.oauth2.details.clientdetails.ClientDetailsServiceImpl;
import com.wing.oauth2.details.userdetails.member.MemberUserDetailsServiceImpl;
import com.wing.oauth2.details.userdetails.user.SysUserDetailsServiceImpl;
import com.wing.oauth2.token.ApiOauthTokenResult;
import com.wing.oauth2.token.CustomJwtAccessTokenConverter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import java.security.KeyPair;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 认证配置
 *
 * @author
 **/
@Log4j2
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final ClientDetailsServiceImpl clientDetailsService;
    private final SysUserDetailsServiceImpl sysUserDetailsService;
    private final WarehouseUserDetailsServiceImpl warehouseUserDetailsService;
    private final MemberUserDetailsServiceImpl memberUserDetailsService;
    private final StringRedisTemplate stringRedisTemplate;

    @Lazy
    @Autowired
    private AuthorizationServerEndpointsConfiguration configuration;


    /**
     * 配置客户端详情
     * @param clients
     */
    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {

        // 配置过滤器，实现Bad client credentials异常自定义信息返回
        String path = "/oauth/token";
        try {
            // 获取自定义映射路径 pathMapping("/oauth/token","/oauth2/admin/user/login")
            path = configuration.oauth2EndpointHandlerMapping().getServletPath(path);
        } catch (Exception e) {
        }
        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(security, path);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());
        security.addTokenEndpointAuthenticationFilter(endpointFilter);
        security.authenticationEntryPoint(authenticationEntryPoint());

        security
                // 支持 client_id 和 client_secret 作登录认证(与 CustomClientCredentialsTokenEndpointFilter 不能同时使用)
                // .allowFormAuthenticationForClients()
                // 获取密钥需要身份认证，使用单点登录时必须配置
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        endpoints
                // 配置认证管理器
                .authenticationManager(authenticationManager)
                // 配置用户详情server，密码模式必须
                .userDetailsService(memberUserDetailsService)
                // 配置TokenServices
                .tokenServices(tokenServices(endpoints))
                // 配置grant_type模式
                .tokenGranter(tokenGranter(endpoints))
                // 配置异常处理
                .exceptionTranslator(new OAuth2ExceptionHandler())
                //自定义登录路径
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .pathMapping("/oauth/token","/oauth2/admin/user/login");
    }

    /**
     * 使用非对称加密算法对token签名
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new CustomJwtAccessTokenConverter();
        converter.setKeyPair(keyPair());
        return converter;
    }

    /**
     * 从classpath下的密钥库中获取密钥对(公钥+私钥)
     */
    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
                new ClassPathResource("wing.jks"), "123456".toCharArray());
        KeyPair keyPair = factory.getKeyPair(
                "wing", "123456".toCharArray());

        // System.out.println("=================私钥===================");
        // PrivateKey privateKey = keyPair.getPrivate();
        // System.out.println(new String(Base64.getEncoder().encode(privateKey.getEncoded())));
        //
        //
        // System.out.println("=================公钥===================");
        // PublicKey publicKey = keyPair.getPublic();
        // System.out.println(new String(Base64.getEncoder().encode(publicKey.getEncoded())));

        return keyPair;
    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 返回的token自定义化
     *
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> new ApiOauthTokenResult(accessToken, authentication);
    }

    /**
     * 自定义认证异常响应数据
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, e) -> {
            String json = handleResponse(JsonResult.fail(JsonResult.Code.INVALID_CLIENT));
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().print(json);
        };
    }

    private String handleResponse(JsonResult jsonResult) throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(jsonResult);
    }

    public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {

        // Token增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        // # jwt转换器
        tokenEnhancers.add(jwtAccessTokenConverter());
        // # 自定义token
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        // # 配置Token增强
        tokenServices.setTokenEnhancer(tokenEnhancerChain);
        // # 配置Token存储
        tokenServices.setTokenStore(tokenStore());
        // # 是否支持刷新Token
        tokenServices.setSupportRefreshToken(true);
        // # 配置客户端详情
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        // 设置accessToken的默认超时时间(如果clientDetails的为null就取默认的，如果clientDetails的不为null取clientDetails中的)
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(1));
        // 设置refreshToken的默认超时时间(如果clientDetails的为null就取默认的，如果clientDetails的不为null取clientDetails中的)
        tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));

        // 多用户体系下，刷新token再次认证客户端ID和 UserDetailService 的映射Map
        Map<String, UserDetailsService> clientUserDetailsServiceMap = new HashMap<>();
        // 系统管理客户端
        clientUserDetailsServiceMap.put(SecurityConstants.ADMIN_CLIENT_ID, sysUserDetailsService);
        // Android、IOS、H5 移动客户端
        clientUserDetailsServiceMap.put(SecurityConstants.APP_CLIENT_ID, memberUserDetailsService);
        // 微信小程序客户端
        clientUserDetailsServiceMap.put(SecurityConstants.WEAPP_CLIENT_ID, memberUserDetailsService);
        // 微信小程序客户端
        clientUserDetailsServiceMap.put(SecurityConstants.WXAPP_CLIENT_ID, sysUserDetailsService);
        clientUserDetailsServiceMap.put(SecurityConstants.WAREHOUSE_CLIENT_ID, warehouseUserDetailsService);


        // 刷新token模式下，重写预认证提供者替换其AuthenticationManager，可自定义根据客户端ID和认证方式区分用户体系获取认证用户信息
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(new PreAuthenticatedUserDetailsService<>(clientUserDetailsServiceMap));
        tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
        return tokenServices;

    }

    /**
     * 创建grant_type列表
     * @param endpoints
     * @return
     */
    public TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {

        // 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
        List<TokenGranter> list = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

        // 添加验证码授权模式授权者
        list.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager, stringRedisTemplate
        ));
        // 添加手机短信验证码授权模式的授权者
        list.add(new SmsCodeTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager
        ));
        // 添加微信授权模式的授权者
        list.add(new WechatTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager
        ));
        list.add(new WarehouseTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
                endpoints.getOAuth2RequestFactory(), authenticationManager
        ));

        return new CompositeTokenGranter(list);
    }

}
